package com.heima.schedule.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.common.redis.CacheService;
import com.heima.model.schedule.dtos.TaskDto;
import com.heima.model.schedule.pojos.Taskinfo;
import com.heima.model.schedule.pojos.TaskinfoLogs;
import com.heima.schedule.mapper.TaskinfoLogsMapper;
import com.heima.schedule.mapper.TaskinfoMapper;
import com.heima.schedule.service.TaskinfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * @author jack
 * @data 2024 9:38
 */
@Service
@Transactional
@Slf4j
public class TaskinfoServiceImpl extends ServiceImpl<TaskinfoMapper, Taskinfo> implements TaskinfoService {

    @Autowired
    private TaskinfoLogsMapper taskinfoLogsMapper;
    @Autowired
    private CacheService cacheService;

    /**
     * 添加任务
     *
     * @param taskDto
     */
    @Override
    public void addTask(TaskDto taskDto) {
        //1. 添加任务到DB
        addTaskToDB(taskDto);

        //2. 根据任务时间，选择将任务添加到Redis
        addTaskToRedis(taskDto);
    }

    /**
     * 拉取任务
     *
     * @param taskType
     * @param priority
     * @return
     */
    @Override
    public TaskDto pullTask(Integer taskType, Integer priority) {
        //1. 根据任务类型和任务优先级来拼接redis中对应的List的key
        String key = "topic_"+taskType+"_"+priority;

        //2. 根据key将任务从redis的List中弹出
        String taskDtoJSON = cacheService.lRightPop(key);
        TaskDto taskDto = JSON.parseObject(taskDtoJSON, TaskDto.class);

        if (taskDto != null) {
            //3. 将任务从DB的taskinfo中移除
            removeById(taskDto.getTaskId());

            //4. 将taskinfoLogs中状态修改为：1=EXECUTED
            TaskinfoLogs logs = new TaskinfoLogs();
            logs.setTaskId(taskDto.getTaskId());
            logs.setStatus(1);
            taskinfoLogsMapper.updateById(logs);
        }
        return taskDto;
    }

    /**
     * 定时刷新数据库到redis
     * 执行频次： 每五分钟执行一次
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void refreshDBToRedis(){
        log.info("==========定时刷新DB数据到Redis中===================");
        //将数据库中5分钟内要执行的任务数据查出来
        //5分钟内的时间
        Date futureTime = new Date(System.currentTimeMillis() + (1000 * 60 * 5));

        //当前系统时间 < 任务执行时间 <= 预设时间：存入ZSet
        List<Taskinfo> taskinfos = list(Wrappers.<Taskinfo>lambdaQuery()
                .le(Taskinfo::getExecuteTime, futureTime));

        if (taskinfos != null && taskinfos.size() > 0){
            log.info("==========开始刷新DB数据到Redis中===================");
            //存入list中，注意：存之前一定要清除原有数据
            Set<String> listKeys = cacheService.scan("topic_*");
            cacheService.delete(listKeys);
            Set<String> zsetKeys = cacheService.scan("future_*");
            cacheService.delete(zsetKeys);

            for (Taskinfo taskinfo : taskinfos) {
                TaskDto taskDto = new TaskDto();
                BeanUtils.copyProperties(taskinfo,taskDto);
                taskDto.setExecuteTime(taskinfo.getExecuteTime().getTime());
                addTaskToRedis(taskDto);
            }
        }
    }

    /**
     * 定时刷新ZSet数据到List中
     * 执行频次： 每分钟执行一次
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void refreshZSetToList(){
        log.info("==========定时刷新ZSet数据到List中===================");
        Set<String> zsetKeys = cacheService.scan("future_*");
        for (String zsetKey : zsetKeys) {
            String[] split = zsetKey.split("future_");      // ["","1001_1"]
            String listKey = "topic_"+split[1];                     // topic_1001_1

            //将ZSet中任务的执行时间（分数） <= 当前时间的数据取出来
            Set<String> taskDtos = cacheService.zRangeByScore(zsetKey, 0, System.currentTimeMillis());
            cacheService.refreshWithPipeline(zsetKey,listKey,taskDtos);
        }
    }

    //添加任务到redis
    private void addTaskToRedis(TaskDto taskDto) {
        //1. 拿到一些时间
        //a. 当前时间
        long now = System.currentTimeMillis();
        //b. 预设时间：当前时间往后延迟5分钟的时间
        long futureTime = now + (1000 * 60 * 5);

        //c. 任务的执行时间
        long executeTime = taskDto.getExecuteTime();

        //2. 根据任务执行时间判断当前任务是否存入redis
        if (executeTime <= now){
            //存入List中          topic_1001_1
            String key = "topic_"+taskDto.getTaskType()+"_"+ taskDto.getPriority();
            cacheService.lLeftPush(key, JSON.toJSONString(taskDto));
        }else if (executeTime  <= futureTime){
            //存入ZSet中          future_1001_1
            String key = "future_"+taskDto.getTaskType()+"_"+ taskDto.getPriority();
            cacheService.zAdd(key, JSON.toJSONString(taskDto),taskDto.getExecuteTime());
        }

    }

    //添加任务到DB
    private void addTaskToDB(TaskDto taskDto) {
        //1. 将任务数据保存到 taskinfo
        Taskinfo taskinfo = new Taskinfo();
        BeanUtils.copyProperties(taskDto,taskinfo);
        taskinfo.setExecuteTime(new Date(taskDto.getExecuteTime()));
        save(taskinfo);
        taskDto.setTaskId(taskinfo.getTaskId());

        //2. 将任务进行日志保存：taskinfo_logs
        TaskinfoLogs logs = new TaskinfoLogs();
        BeanUtils.copyProperties(taskinfo,logs);
        logs.setVersion(1);
        logs.setStatus(0);  //0=init  1=EXECUTED
        taskinfoLogsMapper.insert(logs);
    }


}
